package server.command.client;

import java.util.ArrayList;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import app_kvServer.KVServer;
import app_kvServer.ServerMetadata;
import client.KVStore;
import server.command.ecs.*;
import utils.SynchronizedAccessStorageUtil;
import common.messages.KVMessage;
import common.messages.KVMessage.StatusType;


public class ClientInputHandler {
	private boolean isECSClient; 
	private KVServer server;
	
	/**
	 * A list of all commands supported by the server
	 */
	//TODO: please update the server for the Echo Command !
	public enum CommandType { 
		/** Client commands **/
		ECHO, 
		PUT, GET, DELETE, 
		LIST_KEYS, LIST_VALUES, 
		/** ECS commands **/
		INIT_SERVER, START_SERVER, STOP_SERVER, SHUTDOWN_SERVER, 
		LOCK_WRITE, UNLOCK_WRITE, 
		MOVE_DATA, UPDATE_META, UPDATE_RANGE
	};
	
	public static final String DELIMETER = KVMessage.SEPARATOR;
	public static final String COMMAND_PATTERN = ".*"+ KVMessage.SEPARATOR +".*"+ KVMessage.SEPARATOR +".*";
	
	private static Logger logger = Logger.getRootLogger();
	private SynchronizedAccessStorageUtil storageUtil;
	private ICommandInvoker commandInvoker;
	
	public ClientInputHandler(boolean isECS, KVServer server) {
		storageUtil = new SynchronizedAccessStorageUtil();
		commandInvoker = new StorageCommandInvoker();
		this.isECSClient = isECS;
		this.server = server;
	}
	
	/**
	 * Handles all possible requests received from clients. Recognizes and executes commands 
	 * listed within the CommandType enum
	 * @param latestMsg
	 * @return
	 */
	public String processCommand(String msg) {
		logger.info("Input from client: "+msg);
		CommandType command = toCommandType(msg);
		System.out.println(msg);
		try {
			if(Pattern.matches(COMMAND_PATTERN, msg)) {
				int sharpsInd1 = msg.indexOf(KVMessage.SEPARATOR);
				int sharpsInd2 = msg.lastIndexOf(KVMessage.SEPARATOR);
				String parameter1 = msg.substring(sharpsInd1+3, sharpsInd2);
				String parameter2 = msg.substring(sharpsInd2+3);
				logger.info("Recognized command: "+command+" ("+parameter1+", "+parameter2+")");
				/** ECS specific commands **/
				if(isECSClient) {
					switch (command) {
						case INIT_SERVER:
							ArrayList<ServerMetadata> initMeta = ServerMetadata.unmarshal(parameter1);
							return (String) commandInvoker.execute(new CommandInitServer(server, initMeta));
						case START_SERVER:
							return (String) commandInvoker.execute(new CommandStartServer(server));
						case STOP_SERVER:
							return (String) commandInvoker.execute(new CommandStopServer(server));
						case SHUTDOWN_SERVER:
							return (String) commandInvoker.execute(new CommandShutdown(server));
						case LOCK_WRITE:
							return (String) commandInvoker.execute(new CommandLockWrite(storageUtil));
						case UNLOCK_WRITE:
							return (String) commandInvoker.execute(new CommandUnlockWrite(storageUtil));
						case MOVE_DATA:
							String[] destination = parameter1.split(":");
							String[] range = parameter2.split(":");
							
							String serverIp = destination[0];
							int serverPort = Integer.parseInt(destination[1]);
							String rangeStart = range[0], 
							       rangeEnd   = range[1];
							return (String) commandInvoker.execute(new CommandMoveData(storageUtil, 
									rangeStart, rangeEnd, serverIp, serverPort));
						case UPDATE_META:
							ArrayList<ServerMetadata> updMeta = ServerMetadata.unmarshal(parameter1);
							return (String) commandInvoker.execute(new CommandUpdateMeta(server, updMeta));
						case UPDATE_RANGE:
							return (String) commandInvoker.execute(new CommandUpdateRange(server, parameter1, parameter2));
							
					}
				} 
				/** Ignore client requests in a close state **/
				else if (server.getServerState()==KVServer.ServerState.CLOSED) {
					return ClientInputHandler.composeClientResponse(StatusType.SERVER_STOPPED, parameter1, parameter2);
				}
				/** Common storage commands **/
				switch (command) {
					case PUT:
						return (String) commandInvoker.execute(new CommandPut(server, storageUtil, parameter1, parameter2));
					case GET:
						return (String) commandInvoker.execute(new CommandGet(server, storageUtil, parameter1));
					case DELETE:
						return (String) commandInvoker.execute(new CommandDelete(server, storageUtil, parameter1));
					case LIST_KEYS:
						return (String) commandInvoker.execute(new CommandListKeys(storageUtil));
					case LIST_VALUES:
						return (String) commandInvoker.execute(new CommandListValues(storageUtil));
				}
			}
		} catch (Exception exc) {
			StatusType errorStatus = getErrorStatus(command);
			logger.error("An error thrown while parsing and executing a command"+exc.getMessage());
			return composeClientResponse(errorStatus, "ERROR: " + exc.getMessage(), "");
		}
		/** If this line is reached => no command was properly executed => the input was invalid **/
		StatusType errorStatus = getErrorStatus(command);
		logger.warn("Unrecognized command");
		return composeClientResponse(errorStatus, "ERROR: invalid input", "");
	}
	
	public static CommandType toCommandType(String input) {
		input = input.toUpperCase();
		for (CommandType command: CommandType.values() ) {
			if(input.startsWith(command.name()+DELIMETER ) ) {
				return command;
			}
		}
		return null;
	}
	
	public static StatusType getErrorStatus(CommandType command) {
		StatusType[] statuses = StatusType.values();
		for (StatusType status: statuses) {
			if (status.name().equalsIgnoreCase(command+"_ERROR") ) {
				return status;
			}
		}
		return null;
	}
	
	public static String composeClientResponse(StatusType status, String parameter1, String parameter2) {
		return status+DELIMETER+parameter1+DELIMETER+parameter2;
	}
	
}
